﻿using System;
using System.Data;
using System.Configuration;
using System.Data.SqlClient;
using System.Collections.Generic;

namespace Optum.SN.Infra.Core.Cryptography
{
    /// <summary>
    /// Provides cryptographic API.
    /// </summary>
    public class SymmetricCrypto
    {
        /// <summary>
        /// Encrypts a list of plain text data elements.
        /// </summary>
        /// <param name="plainTextList">The list of data elements to encrypt.</param>
        /// <returns>List of <see cref="CryptoUpdater"/> items, where each item contains the original data element
        /// and the corresponding cipher text.</returns>
        public CryptoUpdaterList Encrypt(List<string> plainTextList)
        {
            return DoCryptoList("fn_EncryptList", new CryptoUpdaterList(plainTextList, true));
        }
        /// <summary>
        /// Encrypts a list of plain text comma separated data elements.
        /// </summary>
        /// <param name="plainTextCsv">The list of comma separated data elements to encrypt.</param>
        /// <returns>List of <see cref="CryptoUpdater"/> items, where each item contains the original data element
        /// and the corresponding cipher text.</returns>
        public CryptoUpdaterList EncryptCsv(string plainTextCsv)
        {
            return Encrypt(new List<string>(plainTextCsv.Split(",".ToCharArray())));
        }
        /// <summary>
        /// Encrypts a data element.
        /// </summary>
        /// <param name="plainText">The data to encrypt.</param>
        /// <returns>Cipher text as Base64 string.</returns>
        public string Encrypt(string plainText)
        {
            // Check arguments. 
            ValidateParameter("plainText", plainText);
            return DoCrypto("fn_Encrypt", plainText);
        }
        /// <summary>
        /// Decrypts a list of cipher text data elements.
        /// </summary>
        /// <param name="cipherTextList">The list of data elements to decrypt.</param>
        /// <returns>List of <see cref="CryptoUpdater"/> items, where each item contains the original cipher data element
        /// and the corresponding plain text.</returns>
        public CryptoUpdaterList Decrypt(List<string> cipherTextList)
        {
            return DoCryptoList("fn_DecryptList", new CryptoUpdaterList(cipherTextList, false));
        }
        /// <summary>
        /// Decrypts a list of comma separated cipher text data elements.
        /// </summary>
        /// <param name="cipherTextCsv">The list of comma separated data elements to decrypt.</param>
        /// <returns>List of <see cref="CryptoUpdater"/> items, where each item contains the original cipher data element
        /// and the corresponding plain text.</returns>
        public CryptoUpdaterList DecryptCsv(string cipherTextCsv)
        {
            return Decrypt(new List<string>(cipherTextCsv.Split(",".ToCharArray())));
        }
        /// <summary>
        /// Decrypts cipher text in Base64 string.
        /// </summary>
        /// <param name="cipherText">The data to decrypt.</param>
        /// <returns>Decrypted data.</returns>
        public string Decrypt(string cipherText)
        {
            // Check arguments. 
            ValidateParameter("cipherText", cipherText);
            return DoCrypto("fn_Decrypt", cipherText);
        }

        /// <summary>
        /// Validates parameter values.
        /// </summary>
        /// <param name="parameterName">Name of the parameter to use in exception when validation fails.</param>
        /// <param name="parameterValue">Value of the parameter to validate.</param>
        /// <exception cref="ArgumentNullException">Thrown when parameterValue is null, empty, or white space.</exception>
        private void ValidateParameter(string parameterName, string parameterValue)
        {
            if (string.IsNullOrEmpty(parameterValue) || parameterValue.Trim() == "")
                throw new ArgumentNullException(parameterName);
        }
        /// <summary>
        /// Gets database connection from configuration.
        /// </summary>
        private SqlConnection DbConnection
        {
            get
            {
                return new SqlConnection(ConfigurationManager.ConnectionStrings["CryptoConnectionString"].ToString());
            }
        }
        /// <summary>
        /// En/decrypts a data element.
        /// </summary>
        /// <param name="udfName">The crypto User Defined Function name, e.g., fn_Encrypt or fn_Decrypt.</param>
        /// <param name="cryptoText">Source data to en/decrypt.</param>
        /// <returns>En/decrypted data element.</returns>
        private string DoCrypto(string udfName, string cryptoText)
        {
            using (var cn = DbConnection)
            {
                cn.Open();
                using (var cmd = new SqlCommand(string.Format("SELECT dbo.{0}('{1}')", udfName, cryptoText), cn))
                    return cmd.ExecuteScalar().ToString();
            }
        }
        /// <summary>
        /// En/decrypts a data list.
        /// </summary>
        /// <param name="udfName">The crypto User Defined Function name, e.g., fn_EncryptList or fn_DecryptList.</param>
        /// <param name="cryptoList">Source data list to en/decrypt.</param>
        /// <returns>En/decrypted data list.</returns>
        private CryptoUpdaterList DoCryptoList(string udfName, CryptoUpdaterList cryptoList)
        {
            using (var dt = cryptoList.ToDataTable())
            using (var cn = DbConnection)
            {
                cn.Open();
                using (var cmd = new SqlCommand(string.Format("SELECT * FROM dbo.{0}(@cryptoData)", udfName), cn))
                {
                    cmd.Parameters.Add(new SqlParameter("@cryptoData", SqlDbType.Structured)
                    {
                        Value = dt,
                        TypeName = "dbo.CryptoUpdater"
                    });
                    cmd.CommandTimeout = 0;
                    return new CryptoUpdaterList(cmd.ExecuteReader());
                }
            }
        }
    }
}